﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Text;

namespace MBEditor
{
    partial class Editor
    {
        private bool List(string lineNum)
        {
            bool succeeded = false;
            if (storedLines.Count > 0)
            {
                if (!String.IsNullOrEmpty(lineNum))
                {
                    int statement;
                    if (Int32.TryParse(lineNum, out statement) && storedLines.ContainsKey(statement))
                    {
                        Console.WriteLine("{0} {1}", statement, storedLines[statement]);
                        succeeded = true;
                    }
                    else
                    {
                        DisplayError("Invalid statement number given to list", true);
                    }
                }
                else
                {
                    foreach (KeyValuePair<int, string> line in storedLines)
                    {
                        Console.WriteLine("{0} {1}", line.Key, line.Value);
                    }
                    succeeded = true;
                }
            }
            else
            {
                DisplayError("No stored code", true);
            }
            return succeeded;
        }

        private bool Save(string fileName)
        {
            bool succeeded = false;
            if (!fileName.EndsWith(".mbi", true, null))
            {
                fileName += ".mbi";
            }
            CD(null);
            DisplayStatus("Save in this directory (Y/N): ", false);
            string input = null;
            do
            {
                input = Console.ReadLine();
                if (!String.IsNullOrEmpty(input))
                {
                    input = input.ToLower();
                    if (input[0] == 'y' || input[0] == 'n')
                    {
                        break;
                    }
                }
                else return false;
            }
            while(true);
            if(input[0] == 'n')
            {
                if (saver == null)
                {
                    saver = new SaveFileDialog();
                    saver.DefaultExt = "mbi";
                    saver.Filter = "MiniBasic Files (*.mbi)|*.mbi|All Files|*.*";
                    saver.FilterIndex = 1;
                    saver.InitialDirectory = Environment.CurrentDirectory;
                    saver.OverwritePrompt = true;
                }
                saver.FileName = fileName;
                DialogResult res = saver.ShowDialog(EditorUtils.GetConsoleWindow());
                if (res != DialogResult.OK)
                {
                    return false;
                }
                fileName = saver.FileName;
            }
            else
            {
                try
                {
                    fileName = Path.Combine(Environment.CurrentDirectory, fileName);
                    using (Stream file = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                    {
                        Encoding fileEncoding = Encoding.ASCII;
                        BinaryWriter bw = new BinaryWriter(file, fileEncoding);
                        bw.Write(MB_MAGIC);
                        foreach (KeyValuePair<int, string> line in storedLines)
                        {
                            bw.Write(line.Key);
                            string fullline = " " + line.Value + '\r';
                            // can't write a string directly
                            // as that would prefix the string length
                            // to the output
                            bw.Write(fileEncoding.GetBytes(fullline));
                        }
                    }
                    succeeded = true;
                }
                catch (Exception ex)
                {
                    DisplayError(ex.Message, true);
                }
            }
            return succeeded;
        }

        private static Stream LoadFile(ref string fileName)
        {
            Stream fileStream = null;
            string originalName = fileName;
tryAgain:
            try
            {
                // try and open the filename given by the user
                fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
            }
            catch(System.IO.FileNotFoundException)
            {
                if (!fileName.EndsWith(".mbi", true, null))
                {
                    fileName += ".mbi";
                    goto tryAgain;
                }
                if (opener == null)
                {
                    opener = new OpenFileDialog();
                    opener.DefaultExt = "mbi";
                    opener.Title = "Couldn't open file, Please select another";
                    opener.InitialDirectory = Environment.CurrentDirectory;
                    opener.CheckFileExists = true;
                    opener.Filter = "MiniBasic Files (*.mbi)|*.mbi|All Files|*.*";
                    opener.FilterIndex = 1;
                    opener.Multiselect = false;
                }
                opener.FileName = originalName;
                if (opener.ShowDialog(EditorUtils.GetConsoleWindow()) == DialogResult.OK)
                {
                    fileName = opener.FileName;
                    fileStream = opener.OpenFile();
                }
            }
            catch(Exception ex)
            {
                DisplayError("Couldn't load file " + fileName + " because of error " + ex.Message, true);
            }
            return fileStream;
        }

        private bool Load(string fileName)
        {
            bool succeeded = false;
            Stream file = LoadFile(ref fileName);
            if(file != null)
            {
                using (file)
                {
                    try
                    {
                        BinaryReader reader = new BinaryReader(file, Encoding.ASCII);
                        long length = file.Length;
                        int magic = reader.ReadInt32();
                        if (magic == MB_MAGIC)
                        {
                            SortedDictionary<int, string> fileLines = new SortedDictionary<int, string>();
                            StringBuilder sb = new StringBuilder();
                            long currentPos = 4;
                            while (currentPos != length)
                            {
                                sb.Remove(0, sb.Length);
                                int line = reader.ReadInt32();
                                currentPos += 4;
                                byte thisByte;
                                while ((thisByte = reader.ReadByte()) != '\r')
                                {
                                    sb = sb.Append((char)thisByte);
                                }
                                fileLines.Add(line, sb.ToString());
                                currentPos += sb.Length + 1;
                            }
                            succeeded = true;
                            storedLines.Clear();
                            foreach (KeyValuePair<int, string> fileLine in fileLines)
                            {
                                storedLines.Add(fileLine.Key, fileLine.Value);
                            }
                        }
                        else
                        {
                            DisplayError("Invalid MiniBasic file (incorrect signature)", true);
                        }
                    }
                    catch (Exception ex)
                    {
                        DisplayError(ex.Message, true);
                    }
                }
            }
            return succeeded;
        }

        private static bool RunFile(string file)
        {
            bool succeeded = false;
            string absolute = Path.GetFullPath(file);
            if (File.Exists(absolute))
            {
                string error = null;
                try
                {
                    Parser parser = new Parser();
                    error = parser.ExecuteFile(absolute);
                    DisplayStatus("\nExecution has ended\n", true);
                }
                catch (ParserUnavailableException parserExcept)
                {
                    error = "parser unavailable - " + parserExcept.Message;
                }
                catch (Exception ex)
                {
                    error = ex.Message;
                }
                succeeded = (error == null);
                if (!succeeded)
                {
                    DisplayError(error, true);
                }
            }
            else
            {
                DisplayError("File \"" + absolute + "\" doesn't exist", true);
            }
            return succeeded;
        }

        private bool Run(string file)
        {
            bool succeeded = false;
            if (!String.IsNullOrEmpty(file))
            {
                succeeded = RunFile(file);
            }
            else
            {
                succeeded = RunString(EditorUtils.CombineLinesToString(storedLines));
            }
            return succeeded;
        }

        private void New()
        {
            storedLines.Clear();
        }

        private static bool CD(string path)
        {
            bool success = true;
            if(String.IsNullOrEmpty(path))
            {
                DisplayStatus("Current Directory is: " + Environment.CurrentDirectory, true);
            }
            else
            {
                path = Path.GetFullPath(path);
                if (Directory.Exists(path))
                {
                    Environment.CurrentDirectory = path;
                }
                else
                {
                    DisplayError("directory " + path + " doesn't exist", true);
                    success = false;
                }
            }
            return success;
        }

        private static void DisplayHelp()
        {
            const string helpString =
                "Load\tStores a given minibasic code file in memory\n" +
                "Save\tSaves the stored code into a specified file\n" +
                "List\tDisplays the stored minibasic code\n" +
                "New \tClears any stored code\n" +
                "Run \tExecutes the stored minibasic code, or a specified file if one is given\n" +
                "CD  \tDisplays the current directory or sets it to a given path\n" +
                "CLS \tClears the screen contents\n" + // This command is implemented in the minibasic implementation
                "Help/?\tDisplay this text\n" +
                "Bye \tExits the editor\n";
            const string additional = "Code is entered in the format of:\n" +
                "\tstatement_number space commands\n" +
                "\te.g. 15 print \"hello\"\n" +
                "If the statement number already exists in stored memory it is overwritten.\n" +
                "Code entered without a statement number is executed directly after input\n" +
                "rather than stored.\n";
            DisplayError("\rBuilt-in Commands", true);
            DisplayStatus(helpString, true);
            DisplayError("\rOther", true);
            DisplayStatus(additional, true);
        }

        public bool TestIfAndCallBuiltinFunc(string firstWord, string restOfLine)
        {
            switch(firstWord)
            {
                case "list":
                {
                    List(restOfLine);
                }
                break;
                case "save":
                {
                    Save(restOfLine);
                }
                break;
                case "load":
                {
                    Load(restOfLine);
                }
                break;
                case "run":
                {
                    Run(restOfLine);
                    ResetConsoleColors();
                }
                break;
                case "new":
                {
                    New();
                }
                break;
                case "cd":
                {
                    CD(restOfLine);
                }
                break;
                case "help":
                case "?":
                {
                    DisplayHelp();
                }
                break;
                default:
                {
                    return false;
                }
            }
            return true;
        }

        public static bool RunString(string program)
        {
            string error = null;
            try
            {
                Parser parser = new Parser();
                error = parser.ExecuteString(EditorUtils.StringTo850ByteArray(program), true);
                DisplayStatus("\nExecution has ended\n", true);
            }
            catch (ParserUnavailableException ex)
            {
                error = "Parser unavailable (" + ex.Message + ")";
            }
            catch(Exception ex)
            {
                error = ex.Message;
            }
            if(error != null)
            {
                DisplayError(error, true);
            }
            return error == null;
        }
    }
}
